# -*- coding: utf-8 -*-

import sys
import os

# Adds our "Modules" folder to our environment path
path = os.path.join(request.folder, 'modules')
if not path in sys.path:
    sys.path.append(path)

#from gluon.contrib.markmin.markmin2html import markmin2html
from forumhelper import ForumHelper
if False:
    from gluon.tools import *

# Make Systemwide methods/classes available
# db and auth defined in db.py
forumhelper = ForumHelper(globals())

# Control Migrations
migrate = True  # False if DB Schema already exists (? - Read Docs Pls)
fake_migrate = False  # True to regen table info for EXISTING tables (run once

# Memberships used in pyforum:
forum_memberships = {
    'FORUM_ADMINISTRATOR': 'fAdministrator',
    'FORUM_MEMBER': 'fMember'
}

# zf_forum_category
# Categories just provide a "container" for each discussion board (zf_forum).
# cat_desc is optional and it may be used to provide a longer description of
# the category
# cat_public is a flag that determines if an anonymous user can view the forum
# cat_visible_to controls the roles for which the category be available for
# viewing (See reasons for leaving this field as a string on the zf_forum
# table definition description next.
db.define_table('zf_forum_category',
                db.Field('cat_name', 'string', length=255, required=True),
                db.Field('cat_desc', 'text', default=''),
                db.Field('cat_visible_to', 'string',
                         length=255, required=True),
                db.Field('cat_sort', 'integer', required=False, default=0),
                migrate='zf_forum_category.table', fake_migrate=fake_migrate)

# zf_forum
# This is the topics container and in itself it a containee of a category.
# All the fields in here should be self explanatory, perhaps the most
# "controversial" part is the "string" declaration for the values of the
# roles (which can be multiple), as opposed to use related tables, but for
# the sake of simplicity (and the fact that python's str.split() is pretty
# fast), I've decided to leave these fields as-is.
db.define_table('zf_forum',
                db.Field('cat_id', db.zf_forum_category),
                db.Field('forum_title', 'string', length=255, required=True),
                db.Field('forum_desc', 'text', default=''),
                db.Field('moderation_flag', 'boolean', required=True,
                         default=False),
                db.Field('anonymous_viewaccess', 'boolean', required=False,
                         default=True),
                db.Field('add_postings_access_roles', 'string', length=255,
                         default=''),
                db.Field('reply_postings_access_roles', 'string', length=255,
                         default=''),
                db.Field('include_latest_topics', 'boolean', required=False,
                         default=True),
                db.Field('forum_sort', 'integer', required=False, default=0),
                db.Field('modifying_date', 'datetime'),
                migrate='zf_forum.table', fake_migrate=fake_migrate)

# zf_topic
# This is the actual topic, it depends exclusively on zf_forum
# Title and Content are the main exposed information pieces.
# parent_id is a "soft" relationship (can be 0), if 0, it means that the topic
#           is a "parent" topic, for non-zero values, it represents that the
#           topic is a "child" topic and the parent topic would be specified
#           in this field. Parent/Child flag also is specified in the
#           "parent_flag" boolean field
# Creation and Modifying user fields correspond to web2py's auth_user.id
# hits has a tally of the number of views this topic has, this value is
#      incremented or updated on "parent" topics only.
# All the *_flag fields should be self-explanatory
# reply_to_topic_id is a field that can be used on threaded discussions,
#                   I am personally not too fond of this as oftentimes these
#                   types of replies can become topics themselves, this value
#                   will be updated if the user "replies" to either the main
#                   parent topic, or one of the children topics.
# ip_address probably will change in a future release, it provides a sort of
#            "signature" or "liability" for the user that is posting a topic
#            or replying to one.
db.define_table('zf_topic',
                db.Field('forum_id', db.zf_forum),
                db.Field('title', 'string', length=255, required=True),
                db.Field('contents', 'text', required=True),
                db.Field('parent_id', 'integer', required=False, default=0),
                db.Field('creation_user_id', 'integer', required=False,
                         default=0),
                db.Field('creation_date', 'datetime', required=True),
                db.Field('modifying_user_id', 'integer', required=False,
                         default=0),
                db.Field('modifying_date', 'datetime', required=True),
                db.Field('hits', 'integer', required=False, default=0),
                db.Field('parent_flag', 'boolean', required=False),
                db.Field('locked_flag', 'boolean', required=False),
                db.Field('disabled_flag', 'boolean', required=False),
                db.Field('sticky_flag', 'boolean', required=False),
                db.Field('poll_flag', 'boolean', required=False),
                db.Field('system_announcement_flag', 'boolean',
                         required=False),
                db.Field('reply_to_topic_id', 'integer', required=False,
                         default=0),
                db.Field('ip_address', 'string', required=True),
                migrate='zf_topic.table', fake_migrate=fake_migrate)

# System Properties - Used by the system itself
db.define_table('zf_system_properties',
                db.Field('property_name', 'string', length=128, required=True),
                db.Field('property_desc', 'text', required=True),
                db.Field('property_value', 'text', required=True),
                migrate='zf_system_properties.table',
                fake_migrate=fake_migrate)

# Member Properties (Skeleton) (Available member Properties)
# This table contains all the properties available to the uses, it can
# be "aliases", public email, etc, etc, see below in "definition of member
# properties for more information.
db.define_table('zf_member_properties_skel',
                db.Field('property_name', 'string', length=128, required=True),
                db.Field('property_desc', 'text', required=True),
                db.Field('member_editable', 'boolean', default=False),
                migrate='zf_member_properties_skel.table',
                fake_migrate=fake_migrate)

# Member Properties (For each member)
# This is the "many-to-one" table that related to the skeleton table above.
db.define_table('zf_member_properties',
                db.Field('user_id', db.auth_user),
                db.Field('property_id', db.zf_member_properties_skel),
                db.Field('property_value', 'string', length=255,
                         required=True),
                migrate='zf_member_properties.table',
                fake_migrate=fake_migrate)

# Member Avatars
db.define_table('zf_member_avatars',
                db.Field('content_type', 'string', length=128, required=True),
                db.Field('user_id', db.auth_user),
                db.Field('avatar_image', 'blob', required=True, default=None),
                db.Field('avatar_active', 'boolean', required=True,
                         default=True),
                migrate='zf_member_avatars.table', fake_migrate=fake_migrate)

# Member Subscriptions (subscription_type =  ENUM('f', 't') --> Forum or Topic
# Since members can subscribe (or unsubscribe) to topics, or entire forums
# this table controls this interaction, if a member subscribes to an entire
# forum, any topic that is created or any existing topic that is updated will
# trigger an entry in this table
# subscription_id represents the primary id of either the topic or the forum
# If a user decides to "unsubscribe" from a forum/topic, the table will just
# update the subscription_active field.
db.define_table('zf_member_subscriptions',
                db.Field('user_id', db.auth_user),
                db.Field('subscription_id', 'integer', required=True,
                         default=0),
                db.Field('subscription_type', 'string', required=True),
                db.Field('subscription_active', 'boolean', default=True,
                         required=True),
                migrate='zf_member_subscriptions.table',
                fake_migrate=fake_migrate)

# Subscription Notifications.
# When a topic/forum is updated, an internal process will grab all subscribed
# users by querying zf_member_subscriptions and will create an entry in this
# table, an EXTERNAL (or cron) process will scan this table and send the
# appropriate message to the intended recipients.
# subscription_type represents one of 'f' or 't'
# subscription_id represents the id of either the topic or the forum.
db.define_table('zf_member_subscriptions_notification',
                db.Field('user_id', db.auth_user),
                db.Field('subscription_id', 'integer', required=True,
                         default=0),
                db.Field('subscription_type', 'string', required=True),
                db.Field('creation_date', 'datetime', required=True),
                db.Field('is_processed', 'boolean', default=False,
                         required=True),
                migrate='zf_member_subscriptions_notification.table',
                fake_migrate=fake_migrate)

# Member Internal Messages
# Logged in Members can communicate among each other
# (if feature is enabled by the administrator)
# Categories will be defined below, but they include "Inbox", "Read", "Trash"
# and the like
db.define_table('zf_pm_categories',
                db.Field('display_order', 'integer', required=True, default=0),
                db.Field('name', 'string', length=128, required=True),
                migrate='zf_pm_categories.table', fake_migrate=fake_migrate)

# Defines an e-communication piece
db.define_table('zf_pm',
                db.Field('cat_id', db.zf_pm_categories),
                db.Field('read_flag', 'boolean', default=False, required=True),
                db.Field('user_id', db.auth_user),
                db.Field('from_user_id', db.auth_user),
                db.Field('subject', 'string', length=255, required=True),
                db.Field('msg', 'text', required=True),
                db.Field('creation_date', 'datetime', required=True),
                migrate='zf_pm.table', fake_migrate=fake_migrate)

# Even though Admins can also be contacted using the PM functionality,
# Anonymous or external entities cannot, this table below allows for that,
# another difference is that the contents of this table will be available
# to any forum administrator, not a single one as in personal messages.
# user_id represents the logged in user that sent a message to the
# administrator, or 0 if the user is Anonymous or External.
db.define_table('zf_admin_messages',
                db.Field('user_id', 'integer', required=False, default=0),
                db.Field('subject', 'string', length=255, required=True),
                db.Field('msg', 'text', required=True),
                db.Field('creation_date', 'datetime', required=True),
                db.Field('read_flag', 'boolean', default=False, required=True),
                migrate='zf_admin_messages.table', fake_migrate=fake_migrate)

# Defines the available languages that will be available to the member's
# preferences, this can also be exposed globally.
db.define_table('zf_available_languages',
                db.Field('language_code', 'string', required=True, length=5),
                db.Field('language_desc', 'string', required=True, length=255),
                db.Field('enabled', 'boolean', required=True, default=True),
                migrate='zf_available_languages.table',
                fake_migrate=fake_migrate)

# In line with most (if not all) forum systems, a "ranking" system is also
# available, it can be disabled globally by the system administrator if it
# is decided not to use it, as a Star Trek fan, I am using the DS9's ranking
# system which will be defined further below.
# rank_value_min represents the number of posts that the user must make in
#                order to reach such rank
# The individual rank-counter will be exposed as a member property
db.define_table('zf_member_rank',
                db.Field('rank_name', 'string', required=True, length=128),
                db.Field('rank_value_min', 'integer', required=True,
                         default=0),
                migrate='zf_member_rank.table', fake_migrate=fake_migrate)

# Inappropriate topics, reportable by members and optionally Anonymous
# users (if global (system) option is enabled).
# topic_id represents the actual topic where the request was made from
# child_id represents a children topic from the main topic_id, if the user
#          is reporting the main topic as inappropriate, then the values of
#          both fields will contain the same value.
# An action on this table should update zf_topic.disabled_flag and the UI
# must account for it and hide the entire topic of the individual child topic
# in question.
db.define_table('zf_topic_inappropriate',
                db.Field('topic_id', db.zf_topic),
                db.Field('child_id', 'integer', required=True),
                db.Field('creation_user_id', 'string', required=True,
                         length=255),
                db.Field('creation_date', 'datetime', required=True),
                db.Field('read_flag', 'boolean', default=False,
                         required=True),
                migrate='zf_topic_inappropriate.table',
                fake_migrate=fake_migrate)

# Not all members are angels
db.define_table('zf_member_banned',
                db.Field('user_id', db.auth_user),
                db.Field('ban_date', 'datetime', required=True),
                migrate='zf_member_banned.table', fake_migrate=fake_migrate)

# Not all IP addresses come from Heaven.
db.define_table('zf_ip_banned',
                db.Field('ip', 'string', length=255, required=True),
                db.Field('ban_date', 'datetime', required=True),
                migrate='zf_ip_banned.table', fake_migrate=fake_migrate)

# Data Creation.
# The following must exist to be considered a successful pyForum install:
# Groups (auth_group) (Known as "Roles" in pyForum's world):
#   - fAdministrator (Site Administrator, will have unrestricted acces)
#   - fMember (Regular Forum Member, non-admin access)
# Member Rankings (zf_member_rank) (Optional, See DB Insert):
# Personal Messages Categories (zf_pm_categories) (Optional)
#   - Inbox, Read, Sent, Trash
# System Properties (zf_system_properties) (See DB Insert)
# Member Properties Skeleton (zf_member_properties_skel) (See DB Insert)
# If the system does not detect any Groups when initializing, then in addition
# to create them, it'll also create the following user in the system:
#   - Administrator (admin@pyforum.org) (auth_user)
#     Containing the following role: Administrator (auth_group)

# User Group Creation. Since pyforum uses its own group membership
# it is not needed for web2py to create the standard [user_groupid]
# group, and instead, it will default all newly created users with the
# Member group (which can be elevated to administrator by another)
# administrator at a later time.
auth.settings.create_user_groups = None

auth_group_cnt = db((
    db.auth_group.role == forum_memberships['FORUM_ADMINISTRATOR']) |
    (db.auth_group.role == forum_memberships['FORUM_MEMBER'])).count(
    db.auth_group.id)

# "Official 2 pyforum roles don't exist, let's create them
if auth_group_cnt == 2:
    # This is a key component of pyforum, and thanks to web2py
    # it becomes quite a breeze. All (new) users must contain the 'Member'
    # role upon creation via web2py auth methods
    member_group_id = auth.id_group(
        forum_memberships['FORUM_MEMBER'])
    auth.settings.everybody_group_id = member_group_id
else:
    if auth_group_cnt == 0:
        pyf_roles = [
            {'role': forum_memberships['FORUM_ADMINISTRATOR'],
             'description': 'Site Administrator (auto generated)'},
            {'role': forum_memberships['FORUM_MEMBER'],
             'description': 'Regular Forum Member (auto generated)'}]
        db.auth_group.bulk_insert(pyf_roles)

        # In Addition, it is more likely that the "administrator" user does
        # not exist under the forum context, let's create one..
        admin_email = 'admin@pyforum.org'
        admin_passwd = forumhelper.gen_tmp_passwd()  # auth.random_password()
        admin_record = db(db.auth_user.email == admin_email).count()
        if admin_record == 0:
            # Does not exist, create the user..
            # [field].validate() returns a tuple with the hashed password
            # and a (potential) error message, handle it..
            hashed_password, error = db.auth_user.password.validate(
                admin_passwd)
            admin_user_id = db.auth_user.insert(first_name='Forum',
                                                last_name='Administrator',
                                                email=admin_email,
                                                password=hashed_password)
            # Get the ID for the administration group in auth_group
            admin_group_id = auth.id_group(
                forum_memberships['FORUM_ADMINISTRATOR'])
            # Add this admin_user in the admin_group_id
            # (add a row in auth_membership)
            auth.add_membership(admin_group_id, admin_user_id)

            # Login Admin automatically
            auth.login_user(db.auth_user(admin_user_id))

            # Finally, add a flag in the session to indicate that the
            # Administrator user has been created
            session.RUN_ONCE = 1
            session.NEW_USER = admin_email
            session.NEW_USER_PASSWD = admin_passwd
    else:
        # Crash Hard! - This should *not* happen, ever.
        raise ValueError('Inconsistent number of roles in the System, '
                         'pyForum needs an administrator and member roles '
                         'to be defined. Please read the documentation or '
                         'reach out to the official forum website for help.')

# Add Member Ranks (Optional)
if db(db.zf_member_rank.id > 0).count() == 0:
    data_list = []
    data_list.append({'rank_value_min': 0,
                      'rank_name': 'Starfleet Ensign'})
    data_list.append({'rank_value_min': 30,
                      'rank_name': 'Starfleet Lieutenant, Junior Grade'})
    data_list.append({'rank_value_min': 50,
                      'rank_name': 'Starfleet Lieutenant'})
    data_list.append({'rank_value_min': 100,
                      'rank_name': 'Starfleet Lieutenant Commander'})
    data_list.append({'rank_value_min': 250,
                      'rank_name': 'Starfleet Commander'})
    data_list.append({'rank_value_min': 400,
                      'rank_name': 'Starfleet Captain'})
    data_list.append({'rank_value_min': 500,
                      'rank_name': 'Starfleet Commodore'})
    data_list.append({'rank_value_min': 800,
                      'rank_name': 'Starfleet Rear Admiral'})
    data_list.append({'rank_value_min': 1000,
                      'rank_name': 'Starfleet Vice Admiral'})
    data_list.append({'rank_value_min': 2000,
                      'rank_name': 'Starfleet Admiral'})
    data_list.append({'rank_value_min': 3000,
                      'rank_name': 'Starfleet Fleet Admiral'})
    db.zf_member_rank.bulk_insert(data_list)

# Personal Messages categories (Optional)
if db(db.zf_pm_categories.id > 0).count() == 0:
    data_list = []
    data_list.append({'name': 'Inbox',
                      'display_order': 1})
    data_list.append({'name': 'Read',
                      'display_order': 2})
    data_list.append({'name': 'Sent',
                      'display_order': 3})
    data_list.append({'name': 'Trash',
                      'display_order': 4})
    db.zf_pm_categories.bulk_insert(data_list)

if db(db.zf_system_properties.id > 0).count() == 0:
    data_list = []
    data_list.append({'property_name': 'zfsp_threads_per_page',
                      'property_desc': 'Threads per Page: Number of results '
                      'per page to show in the topic display section',
                      'property_value': '20'})
    data_list.append({'property_name': 'zfsp_email_pwd_on_signup',
                      'property_desc': 'Email Password Upon Signup: Regadless '
                      'of what signup method is used, the new user will '
                      'receive a welcome email',
                      'property_value': ''})
    data_list.append({'property_name': 'zfsp_topic_teaser_length',
                      'property_desc': 'Topic Teaser Length: The number of '
                      'characters shown for a topic when it is viewed from '
                      'the ""view forum"" page, a value of 0 or an empty '
                      'value disables this option, a suggested value would '
                      'be 250 characters',
                      'property_value': '250'})
    data_list.append({'property_name': 'zfsp_allow_member_avatars',
                      'property_desc': 'Allow Avatars: If empty, users will '
                      'not be given the choice of adding or change their '
                      'avatars, any other value will enable avatars for '
                      'all registered users in the system',
                      'property_value': 'True'})
    data_list.append({'property_name': 'zfsp_system_language',
                      'property_desc': 'System Language: Selects the default '
                      'system language, if invalid it will default to '
                      'English (US)',
                      'property_value': 'en'})
    data_list.append({'property_name': 'zfsp_admin_contact_email',
                      'property_desc': 'Admin Contact: (Important) - The '
                      'forum system uses this value to specify the *From* '
                      'email header for any email that is sent out, please '
                      'use a valid email address that your domain will '
                      'recognize, otherwise your system may not send emails '
                      'at all',
                      'property_value': ''})
    data_list.append({'property_name': 'zfsp_mailserver',
                      'property_desc': 'Mail Server: The mail server to use '
                      'when sending out emails',
                      'property_value': 'localhost'})
    data_list.append({'property_name': 'zfsp_mailserver_username',
                      'property_desc': 'Mail Server: The username to use if '
                      'authenticating against a mail server',
                      'property_value': ''})
    data_list.append({'property_name': 'zfsp_mailserver_passwd',
                      'property_desc': 'Mail Server:  The password to use '
                      'if authenticating against a mail server',
                      'property_value': ''})
    data_list.append({'property_name': 'zfsp_use_ranking_system',
                      'property_desc': 'Use Ranking System: If enabled, the '
                      'system will use its internal ranking system, if empty '
                      'the system will not use a ranking system at all',
                      'property_value': 'True'})
    data_list.append({'property_name': 'zfsp_hot_topic_threshold',
                      'property_desc': 'Hot Topic Threshold: Number of views '
                      'necessary to mark the topic as *hot*',
                      'property_value': '50'})
    data_list.append({'property_name': 'zfsp_member_quota',
                      'property_desc': 'Message Quota: Leave empty to disable '
                      'quotas for messages for your users, any other numeric '
                      'value will represent the number of bytes of allowance, '
                      'an invalid amount will always default to 50Kb per user',
                      'property_value': '50000'})
    data_list.append({'property_name': 'zfsp_system_announcement_max',
                      'property_desc': 'System Announcements View: This '
                      'controls the (maximum) number of system announcements '
                      'that pyForum will display in its right nav, an invalid '
                      'value or zero will show a =-No System Messages-= title',
                      'property_value': '5'})
    data_list.append({'property_name': 'zfsp_latest_postings_max',
                      'property_desc': 'Latest Postings View: This controls '
                      'the (maximum) number of latest postings that pyForum '
                      'will display in its right nav, an invalid value or '
                      'zero will show a =-No Messages-= title',
                      'property_value': '5'})
    data_list.append({'property_name': 'zfsp_responses_per_page',
                      'property_desc': 'Responses per page: Controls the '
                      'amount of responses (children topics) that pyForum '
                      'will show and will add pagination accordingly',
                      'property_value': '15'})
    data_list.append({'property_name': 'zfsp_header_html',
                      'property_desc': 'Header Text: This can contain html '
                      'code and will be shown at the top-right corner of '
                      'pyForum',
                      'property_value': 'Welcome to pyForum'})
    data_list.append({'property_name': 'zfsp_allow_registration',
                      'property_desc': 'Allows User self-registration. Any '
                      'value will allow a member to self-register in the '
                      'forum system, an empty value disables this option',
                      'property_value': 'True'})
    db.zf_system_properties.bulk_insert(data_list)

if db(db.zf_member_properties_skel.id > 0).count() == 0:
    data_list = []
    data_list.append({'property_name': 'zfmp_last_login',
                      'property_desc': 'Last Login',
                      'member_editable': False})
    data_list.append({'property_name': 'zfmp_last_login_ip',
                      'property_desc': 'Last Login IP',
                      'member_editable': False})
    data_list.append({'property_name': 'zfmp_allow_pm_use',
                      'property_desc': 'Allow be Contacted by Other Members',
                      'member_editable': True})
    data_list.append({'property_name': 'zfmp_postings',
                      'property_desc': 'Number of Postings',
                      'member_editable': False})
    data_list.append({'property_name': 'zfmp_locale',
                      'property_desc': 'Language for display',
                      'member_editable': True})
    data_list.append({'property_name': 'zfmp_signature',
                      'property_desc': 'Member Signature',
                      'member_editable': True})
    data_list.append({'property_name': 'zfmp_web_page',
                      'property_desc': 'Member URL',
                      'member_editable': True})
    data_list.append({'property_name': 'zfmp_country',
                      'property_desc': 'Member City/Country',
                      'member_editable': True})
    data_list.append({'property_name': 'zfmp_join_date',
                      'property_desc': 'Join Date',
                      'member_editable': False})
    data_list.append({'property_name': 'zfmp_real_name',
                      'property_desc': 'Member Full Name',
                      'member_editable': True})
    data_list.append({'property_name': 'zfmp_display_name',
                      'property_desc': 'Display Name in the Forum System',
                      'member_editable': True})
    db.zf_member_properties_skel.bulk_insert(data_list)
